using System.Reflection;
using System.Web;
using AndyPike.Commons.Persistence;
using AndyPike.Commons.Persistence.NHibernate;
using Castle.Facilities.Logging;
using Castle.MicroKernel.Registration;
using Castle.MonoRail.Framework.Routing;
using Castle.MonoRail.WindsorExtension;
using Castle.Windsor;
using FluentNHibernate.AutoMap;
using NHibernate;
using Configuration=NHibernate.Cfg.Configuration;

namespace AndyPike.Commons.Web.MonoRail
{
    public abstract class MonoRailNHibernateHttpApplication : HttpApplication, IContainerAccessor
    {
        private readonly Assembly entitiesAssembly;
        protected static IWindsorContainer container;
        protected Assembly webAppAssembly;

        protected MonoRailNHibernateHttpApplication(Assembly entitiesAssembly)
        {
            this.entitiesAssembly = entitiesAssembly;
            webAppAssembly = Assembly.GetCallingAssembly();
        }

        public void Application_OnStart()
        {
            HibernatingRhinos.NHibernate.Profiler.Appender.NHibernateProfiler.Initialize();

            container = new WindsorContainer();

            RegisterFacilities();
            RegisterComponents();
            RegisterControllers();
            RegisterFilters();
            
            ConfigureNHibernate();

            RegisterRoutes(RoutingModuleEx.Engine);
        }
        
        public void Application_OnEnd()
        {
            container.Dispose();
        }

        public IWindsorContainer Container
        {
            get { return container; }
        }

        protected virtual void RegisterControllers()
        {
            container
                .Register(
                AllTypes.Pick().FromAssembly(webAppAssembly)
                    .Configure(c => c.LifeStyle.Transient)
                    .If(c => c.Name.EndsWith("Controller"))
                );
        }

        protected virtual void RegisterFilters()
        {
            container
                .Register(
                AllTypes.Pick().FromAssembly(webAppAssembly)
                    .Configure(c => c.LifeStyle.Transient)
                    .If(c => c.Name.EndsWith("Filter"))
                );
        }

        protected virtual void RegisterComponents()
        {
            container
                .AddComponent<IHttpContext, HttpContextFacade>()
                .AddComponent<INHibernateSessionLocater, NHibernateHttpContextSessionLocater>()
                .AddComponent("repository", typeof(IRepository<>), typeof(NHibernateRepository<>));

            RegisterApplicationComponents();
        }

        protected virtual void RegisterFacilities()
        {
            container
                .AddFacility("logging.facility", new LoggingFacility(LoggerImplementation.Log4net, "Logging.config"))
                .AddFacility<MonoRailFacility>()
                .AddFacility<NHibernateTransactionFacility>();
        }

        protected virtual void ConfigureNHibernate()
        {
            var configuration = new Configuration();
            
            RegisterEntitiesAssembly(configuration, entitiesAssembly);
            
            ISessionFactory sessionFactory = configuration.BuildSessionFactory();
            container.Kernel.AddComponentInstance("ISessionFactory", typeof(ISessionFactory), sessionFactory);
        }

        private void RegisterEntitiesAssembly(Configuration configuration, Assembly assembly)
        {
            var model = new AutoPersistenceModel
            {
                Conventions =
                {
                    GetPrimaryKeyName = (type => "Id"),
                    GetForeignKeyNameOfParent = (type => type.Name + "_Id"),
                    GetForeignKeyName = (info => info.Name + "_Id"),
                    GetTableName = (type => type.Name),
                    GetManyToManyTableName = ((child, parent) => child.Name + "_To_" + parent.Name)
                }
            };

            model
                .AddEntityAssembly(assembly)
                .Where(entity => entity.IsAbstract == false &&
                                 entity.GetInterface("IEntity") != null)
                .Configure(configuration);
        }

        public abstract void RegisterApplicationComponents();
        public abstract void RegisterRoutes(RoutingEngine rules);
    }
}